import unittest
from unittest.mock import patch, MagicMock
import os, json

from components.debug.compare.msquickcmp.common.utils import (
    check_exec_script_file, check_file_or_directory_path, check_input_bin_file_path,
    check_file_size_valid, check_input_args, check_convert_is_valid_used,
    check_locat_is_valid, check_device_param_valid,
    AccuracyCompareException,
    ACCURACY_COMPARISON_INVALID_PATH_ERROR,
    ACCURACY_COMPARISON_INVALID_RIGHT_ERROR,
    ACCURACY_COMPARISON_INVALID_DATA_ERROR,
    ACCURACY_COMPARISON_INVALID_PARAM_ERROR,
    ACCURACY_COMPARISON_INVALID_COMMAND_ERROR,
    ACCURACY_COMPARISON_INVALID_DEVICE_ERROR,
    INVALID_CHARS,
    MAX_DEVICE_ID,
    _check_colon_exist,
    _check_content_split_length,
    _check_shape_number,
    check_max_size_param_valid,
    get_input_path,
    InputShapeError,
    get_shape_not_match_message,
    get_shape_to_directory_name,
    get_dump_data_path, get_batch_index,
    DynamicArgumentEnum,
    BATCH_SCENARIO_OP_NAME,
    get_mbatch_op_name,
    get_batch_index_from_name,
    get_data_len_by_shape,
    safe_delete_path_if_exists, parse_json_file, load_npy_from_buffer
)

class TestCheckFunctions1(unittest.TestCase):

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    @patch('components.debug.compare.msquickcmp.common.utils.os.path.exists')
    @patch('components.debug.compare.msquickcmp.common.utils.os.access')
    def test_check_exec_script_file_not_exist(self, mock_access, mock_exists, mock_logger):
        mock_exists.return_value = False
        with self.assertRaises(AccuracyCompareException) as cm:
            check_exec_script_file('fake_script.sh')
        self.assertEqual(cm.exception.error_info, ACCURACY_COMPARISON_INVALID_PATH_ERROR)
        mock_logger.error.assert_called_once()

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    @patch('components.debug.compare.msquickcmp.common.utils.os.path.exists')
    @patch('components.debug.compare.msquickcmp.common.utils.os.access')
    def test_check_exec_script_file_no_execute_right(self, mock_access, mock_exists, mock_logger):
        mock_exists.return_value = True
        mock_access.return_value = False
        with self.assertRaises(AccuracyCompareException) as cm:
            check_exec_script_file('fake_script.sh')
        self.assertEqual(cm.exception.error_info, ACCURACY_COMPARISON_INVALID_RIGHT_ERROR)
        mock_logger.error.assert_called_once()

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    @patch('components.debug.compare.msquickcmp.common.utils.os.path.isdir')
    @patch('components.debug.compare.msquickcmp.common.utils.os.access')
    def test_check_file_or_directory_path_dir_invalid(self, mock_access, mock_isdir, mock_logger):
        mock_isdir.return_value = False
        with self.assertRaises(AccuracyCompareException):
            check_file_or_directory_path('/some/path', isdir=True)
        mock_logger.error.assert_called_once()

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    @patch('components.debug.compare.msquickcmp.common.utils.os.path.isdir')
    @patch('components.debug.compare.msquickcmp.common.utils.os.access')
    def test_check_file_or_directory_path_dir_no_write(self, mock_access, mock_isdir, mock_logger):
        mock_isdir.return_value = True
        mock_access.return_value = False
        with self.assertRaises(AccuracyCompareException):
            check_file_or_directory_path('/some/path', isdir=True)
        mock_logger.error.assert_called_once()

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    @patch('components.debug.compare.msquickcmp.common.utils.os.path.isfile')
    @patch('components.debug.compare.msquickcmp.common.utils.os.access')
    def test_check_file_or_directory_path_file_invalid(self, mock_access, mock_isfile, mock_logger):
        mock_isfile.return_value = False
        with self.assertRaises(AccuracyCompareException):
            check_file_or_directory_path('/some/file', isdir=False)
        mock_logger.error.assert_called_once()

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    @patch('components.debug.compare.msquickcmp.common.utils.os.path.isfile')
    @patch('components.debug.compare.msquickcmp.common.utils.os.access')
    def test_check_file_or_directory_path_file_no_read(self, mock_access, mock_isfile, mock_logger):
        mock_isfile.return_value = True
        mock_access.return_value = False
        with self.assertRaises(AccuracyCompareException):
            check_file_or_directory_path('/some/file', isdir=False)
        mock_logger.error.assert_called_once()

    @patch('components.debug.compare.msquickcmp.common.utils.check_file_or_directory_path')
    @patch('components.debug.compare.msquickcmp.common.utils.get_input_path')
    @patch('components.debug.compare.msquickcmp.common.utils.os.path.realpath')
    def test_check_input_bin_file_path_bin_and_dir(self, mock_realpath, mock_get_input_path, mock_check_path):
        def side_effect_realpath(arg):
            return arg
        mock_realpath.side_effect = side_effect_realpath

        mock_check_path.return_value = None

        mock_get_input_path.side_effect = lambda path, arr: arr.append(path + "/dummy.bin")

        input_str = "/path/file1.bin,/path/dir1"
        result = check_input_bin_file_path(input_str)
        self.assertIn("/path/file1.bin", result)
        self.assertTrue(any(p.endswith("dummy.bin") for p in result))

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    @patch('components.debug.compare.msquickcmp.common.utils.os.stat')
    def test_check_file_size_valid_too_large(self, mock_stat, mock_logger):
        mock_stat.return_value.st_size = 1024
        size_max = 100
        with self.assertRaises(AccuracyCompareException) as cm:
            check_file_size_valid('somefile', size_max)
        self.assertEqual(cm.exception.error_info, ACCURACY_COMPARISON_INVALID_DATA_ERROR)
        mock_logger.error.assert_called_once()

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    def test_check_input_args_invalid_char(self, mock_logger):
        with self.assertRaises(AccuracyCompareException):
            check_input_args(list(INVALID_CHARS))
        mock_logger.error.assert_called_once()

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    def test_check_convert_is_valid_used_invalid(self, mock_logger):
        with self.assertRaises(AccuracyCompareException):
            check_convert_is_valid_used(False, True, "")
        mock_logger.error.assert_called_once()

        with self.assertRaises(AccuracyCompareException):
            check_convert_is_valid_used(False, False, "custom")
        mock_logger.error.assert_called()

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    def test_check_locat_is_valid_invalid(self, mock_logger):
        with self.assertRaises(AccuracyCompareException):
            check_locat_is_valid(False, True)
        mock_logger.error.assert_called_once()

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    def test_check_device_param_valid_invalid(self, mock_logger):
        with self.assertRaises(AccuracyCompareException):
            check_device_param_valid("abc")
        mock_logger.error.assert_called_once()

        with self.assertRaises(AccuracyCompareException):
            check_device_param_valid(str(MAX_DEVICE_ID + 1))
        mock_logger.error.assert_called()


class TestCheckFunctions2(unittest.TestCase):

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    @patch('components.debug.compare.msquickcmp.common.utils.get_shape_not_match_message')
    def test_check_colon_exist_no_colon(self, mock_get_msg, mock_logger):
        mock_get_msg.return_value = "error msg"
        with self.assertRaises(AccuracyCompareException):
            _check_colon_exist("abc")

        mock_logger.error.assert_called_once_with("error msg")
        mock_get_msg.assert_called_once_with(InputShapeError.FORMAT_NOT_MATCH, "abc")

    def test_check_colon_exist_with_colon(self):
        _check_colon_exist("a:b")

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    @patch('components.debug.compare.msquickcmp.common.utils.get_shape_not_match_message')
    def test_check_content_split_length_empty_second(self, mock_get_msg, mock_logger):
        mock_get_msg.return_value = "error msg"
        with self.assertRaises(AccuracyCompareException):
            _check_content_split_length(["something", ""])
        mock_logger.error.assert_called_once_with("error msg")
        mock_get_msg.assert_called_once_with(InputShapeError.VALUE_TYPE_NOT_MATCH, "")

    def test_check_content_split_length_non_empty(self):
        _check_content_split_length(["foo", "bar"])

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    @patch('components.debug.compare.msquickcmp.common.utils.get_shape_not_match_message')
    def test_check_shape_number_invalid(self, mock_get_msg, mock_logger):
        mock_get_msg.return_value = "error msg"
        with self.assertRaises(AccuracyCompareException):
            _check_shape_number("invalid_shape")

        mock_logger.error.assert_called_once_with("error msg")
        mock_get_msg.assert_called_once_with(InputShapeError.VALUE_TYPE_NOT_MATCH, "invalid_shape")

    def test_check_shape_number_valid(self):
        valid_shape = "123"
        with patch('components.debug.compare.msquickcmp.common.utils.DIM_PATTERN', r'^\d+$'):
            _check_shape_number(valid_shape)

    @patch('components.debug.compare.msquickcmp.common.utils.logger')
    def test_check_max_size_param_valid_negative(self, mock_logger):
        with self.assertRaises(AccuracyCompareException):
            check_max_size_param_valid(-1)

        mock_logger.error.assert_called_once()
        err_msg = mock_logger.error.call_args[0][0]
        self.assertIn("max_cmp_size", err_msg)

    def test_check_max_size_param_valid_zero_and_positive(self):
        check_max_size_param_valid(0)
        check_max_size_param_valid(100)

    @patch('components.debug.compare.msquickcmp.common.utils.os.walk')
    def test_get_input_path(self, mock_walk):
        mock_walk.return_value = [
            ('/root', ('subdir',), ('file1.bin', 'file2.txt')),
            ('/root/subdir', (), ('file3.bin',)),
        ]

        bin_file_path_array = []
        get_input_path('/root', bin_file_path_array)

        expected_paths = ['/root/file1.bin', '/root/subdir/file3.bin']
        self.assertEqual(bin_file_path_array, expected_paths)


class TestYourFunctions(unittest.TestCase):
    @patch("components.debug.compare.msquickcmp.common.utils.os")
    @patch("components.debug.compare.msquickcmp.common.utils.shutil")
    @patch("components.debug.compare.msquickcmp.common.utils.logger")
    def test_get_dump_data_path_is_net_output_true(self, mock_logger, mock_shutil, mock_os):
        mock_os.listdir.return_value = ['12_423_246_4352', 'abc']
        mock_os.path.isdir.side_effect = lambda x: '12_423_246_4352' in x or 'abc' in x
        mock_os.path.join.side_effect = lambda a,b: f"{a}/{b}"
        mock_os.walk.return_value = [
            ('/dump/abc', [], ['file1', 'file2']),
        ]

        dump_data_path, file_is_exist = get_dump_data_path("/dump", is_net_output=True)
        self.assertEqual(dump_data_path, "/dump/abc")
        self.assertTrue(file_is_exist)

    @patch("components.debug.compare.msquickcmp.common.utils.os")
    @patch("components.debug.compare.msquickcmp.common.utils.shutil")
    @patch("components.debug.compare.msquickcmp.common.utils.logger")
    def test_get_dump_data_path_is_net_output_false(self, mock_logger, mock_shutil, mock_os):
        mock_os.path.isdir.side_effect = lambda x: '123456' in x or 'xyz' in x
        mock_os.path.join.side_effect = lambda a,b: f"{a}/{b}"
        def listdir_side_effect(path):
            if path == "/dump":
                return ['123456', 'xyz']
            elif 'modelB' in path:
                return ['fileB1']
            elif 'modelA' in path:
                return ['fileA1']
            return []
        mock_os.listdir.side_effect = listdir_side_effect
        mock_os.walk.return_value = [
            ('/dump/123456/modelA', [], ['fileA1']),
            ('/dump/123456/modelB', [], ['fileB1']),
        ]
        mock_shutil.move = MagicMock()
        dump_data_path, file_is_exist = get_dump_data_path("/dump", is_net_output=False, model_name="modelB")
        self.assertEqual(dump_data_path, "/dump/123456/modelB")
        self.assertTrue(file_is_exist)
        mock_shutil.move.assert_called()

    @patch("components.debug.compare.msquickcmp.common.utils.os")
    @patch("components.debug.compare.msquickcmp.common.utils.logger")
    def test_get_dump_data_path_no_dump_data_dir(self, mock_logger, mock_os):
        mock_os.listdir.return_value = ['abc', 'xyz']
        mock_os.path.isdir.return_value = True
        mock_os.path.join.side_effect = lambda a,b: f"{a}/{b}"
        
        with self.assertRaises(AccuracyCompareException):
            get_dump_data_path("/dump", is_net_output=False)

    def test_get_shape_to_directory_name(self):
        input_shape = "1:3,5;7"
        expected = "1-3_5-7"
        self.assertEqual(get_shape_to_directory_name(input_shape), expected)

    @patch("components.debug.compare.msquickcmp.common.utils.os")
    @patch("components.debug.compare.msquickcmp.common.utils.get_batch_index_from_name")
    def test_get_batch_index_found(self, mock_get_batch_index_from_name, mock_os):
        mock_os.walk.return_value = [
            ("/dump/path", [], ["ascend_mbatch_batch_123", "otherfile"])
        ]
        mock_get_batch_index_from_name.return_value = "123"
        result = get_batch_index("/dump/path")
        self.assertEqual(result, "123")


    @patch("components.debug.compare.msquickcmp.common.utils.os")
    def test_get_batch_index_not_found(self, mock_os):
        mock_os.walk.return_value = [
            ("/dump/path", [], ["file1", "file2"])
        ]
        result = get_batch_index("/dump/path")
        self.assertEqual(result, "")

    @patch("components.debug.compare.msquickcmp.common.utils.get_batch_index")
    def test_get_mbatch_op_name_batch_scenario(self, mock_get_batch_index):
        mock_get_batch_index.return_value = "10"
        om_parser = MagicMock()
        om_parser.get_dynamic_scenario_info.return_value = ("", DynamicArgumentEnum.DYM_BATCH)
        op_name = "op"
        npu_dump_data_path = "/path"
        expected = BATCH_SCENARIO_OP_NAME.format(op_name, "10")
        result = get_mbatch_op_name(om_parser, op_name, npu_dump_data_path)
        self.assertEqual(result, expected)

    def test_get_mbatch_op_name_other_scenario(self):
        om_parser = MagicMock()
        om_parser.get_dynamic_scenario_info.return_value = ("", "OTHER_SCENARIO")
        op_name = "op"
        npu_dump_data_path = "/path"
        result = get_mbatch_op_name(om_parser, op_name, npu_dump_data_path)
        self.assertEqual(result, op_name)

    def test_get_batch_index_from_name(self):
        name = "ascend_mbatch_batch_1234xyz"
        result = get_batch_index_from_name(name)
        self.assertEqual(result, "1234")

    @patch("components.debug.compare.msquickcmp.common.utils.logger")
    def test_get_data_len_by_shape_valid(self, mock_logger):
        shape = [2, 3, 4]
        result = get_data_len_by_shape(shape)
        self.assertEqual(result, 24)

    @patch("components.debug.compare.msquickcmp.common.utils.logger")
    def test_get_data_len_by_shape_invalid(self, mock_logger):
        shape = [2, -1, 4]
        result = get_data_len_by_shape(shape)
        self.assertEqual(result, -1)
        mock_logger.warning.assert_called_once_with("please check your input shape, one dim in shape is -1.")


class TestParseInputShapeToList(unittest.TestCase):
    @patch('components.debug.compare.msquickcmp.common.utils._check_colon_exist')
    @patch('components.debug.compare.msquickcmp.common.utils.dym_shape_range_interaction', return_value=True)
    def test_valid_shape(self, mock_interact, mock_check):
        from components.debug.compare.msquickcmp.common.utils import parse_input_shape_to_list
        input_shape = "tensor1:1,2;tensor2:3,4"
        result = parse_input_shape_to_list(input_shape)
        self.assertEqual(result, [[1, 2], [3, 4]])

    @patch('components.debug.compare.msquickcmp.common.utils._check_colon_exist')
    def test_invalid_shape_format(self, mock_check):
        from components.debug.compare.msquickcmp.common.utils import parse_input_shape_to_list, AccuracyCompareException
        with patch('components.debug.compare.msquickcmp.common.utils.logger.error'), \
             patch('components.debug.compare.msquickcmp.common.utils.get_shape_not_match_message', return_value="error"):
            with self.assertRaises(AccuracyCompareException):
                parse_input_shape_to_list("tensor1-1,2")

    @patch('components.debug.compare.msquickcmp.common.utils._check_colon_exist')
    def test_dim_not_digit(self, mock_check):
        from components.debug.compare.msquickcmp.common.utils import parse_input_shape_to_list
        with self.assertRaises(ValueError):
            parse_input_shape_to_list("tensor1:1,a")


class TestDymShapeRangeInteraction(unittest.TestCase):
    @patch('builtins.input', return_value='y')
    def test_yes_input(self, mock_input):
        from components.debug.compare.msquickcmp.common.utils import dym_shape_range_interaction
        self.assertTrue(dym_shape_range_interaction("prompt"))

    @patch('builtins.input', side_effect=Exception("Input error"))
    def test_input_exception(self, mock_input):
        from components.debug.compare.msquickcmp.common.utils import dym_shape_range_interaction
        self.assertFalse(dym_shape_range_interaction("prompt"))


class TestParseDymShapeRange(unittest.TestCase):
    @patch('components.debug.compare.msquickcmp.common.utils._check_colon_exist')
    @patch('components.debug.compare.msquickcmp.common.utils._check_shape_number')
    @patch('components.debug.compare.msquickcmp.common.utils._check_content_split_length')
    @patch('components.debug.compare.msquickcmp.common.utils.dym_shape_range_interaction', return_value=True)
    @patch('components.debug.compare.msquickcmp.common.utils.logger.info')
    def test_parse_valid_range(self, mock_log, mock_interact, mock_len, mock_shape, mock_colon):
        from components.debug.compare.msquickcmp.common.utils import parse_dym_shape_range
        result = parse_dym_shape_range("tensor1:1,2~3")
        self.assertIsInstance(result, list)


class TestParseArgValue(unittest.TestCase):
    @patch('components.debug.compare.msquickcmp.common.utils.parse_value_by_comma')
    def test_parse(self, mock_parse):
        from components.debug.compare.msquickcmp.common.utils import parse_arg_value
        mock_parse.return_value = [1, 2]
        result = parse_arg_value("1,2;3,4")
        self.assertEqual(result, [[1, 2], [1, 2]])


class TestParseValueByComma(unittest.TestCase):
    def test_valid_values(self):
        from components.debug.compare.msquickcmp.common.utils import parse_value_by_comma
        result = parse_value_by_comma("1,2,3")
        self.assertEqual(result, [1, 2, 3])

    def test_invalid_values(self):
        from components.debug.compare.msquickcmp.common.utils import parse_value_by_comma, AccuracyCompareException
        with patch('components.debug.compare.msquickcmp.common.utils.logger.error'):
            with self.assertRaises(AccuracyCompareException):
                parse_value_by_comma("1,a")


class TestExecuteCommand(unittest.TestCase):
    @patch('components.debug.compare.msquickcmp.common.utils.subprocess.Popen')
    @patch('components.debug.compare.msquickcmp.common.utils.filter_cmd', return_value=['echo', 'test'])
    def test_command_success(self, mock_filter, mock_popen):
        from components.debug.compare.msquickcmp.common.utils import execute_command
        mock_proc = MagicMock()
        mock_proc.poll.side_effect = [None, 0]
        mock_proc.stdout.readline.side_effect = [b'output\n', b'']
        mock_proc.returncode = 0
        mock_popen.return_value = mock_proc
        with patch('components.debug.compare.msquickcmp.common.utils.logger.info'):
            execute_command(['echo', 'test'])

    @patch('components.debug.compare.msquickcmp.common.utils.subprocess.Popen')
    @patch('components.debug.compare.msquickcmp.common.utils.filter_cmd', return_value=['bad', 'cmd'])
    def test_command_fail(self, mock_filter, mock_popen):
        from components.debug.compare.msquickcmp.common.utils import execute_command, AccuracyCompareException
        mock_proc = MagicMock()
        mock_proc.poll.side_effect = [None, 0]
        mock_proc.stdout.readline.return_value = b'error\n'
        mock_proc.returncode = 1
        mock_popen.return_value = mock_proc
        with patch('components.debug.compare.msquickcmp.common.utils.logger.error'):
            with self.assertRaises(AccuracyCompareException):
                execute_command(['bad', 'cmd'])


class TestCreateDirectory(unittest.TestCase):
    @patch('components.debug.compare.msquickcmp.common.utils.os.path.exists', return_value=False)
    @patch('components.debug.compare.msquickcmp.common.utils.ms_makedirs')
    def test_create_success(self, mock_mkdirs, mock_exists):
        from components.debug.compare.msquickcmp.common.utils import create_directory
        create_directory('/tmp/fake')

    @patch('components.debug.compare.msquickcmp.common.utils.os.path.exists', return_value=False)
    @patch('components.debug.compare.msquickcmp.common.utils.ms_makedirs', side_effect=OSError("Permission denied"))
    def test_create_fail(self, mock_mkdirs, mock_exists):
        from components.debug.compare.msquickcmp.common.utils import create_directory, AccuracyCompareException
        with patch('components.debug.compare.msquickcmp.common.utils.logger.error'):
            with self.assertRaises(AccuracyCompareException):
                create_directory('/tmp/fake')


class TestSaveNumpyData(unittest.TestCase):
    @patch('components.debug.compare.msquickcmp.common.utils.os.path.exists', return_value=False)
    @patch('components.debug.compare.msquickcmp.common.utils.ms_makedirs')
    @patch('components.debug.compare.msquickcmp.common.utils.np.save')
    def test_save(self, mock_save, mock_mkdirs, mock_exists):
        from components.debug.compare.msquickcmp.common.utils import save_numpy_data
        save_numpy_data('/tmp/data.npy', [1, 2, 3])


class TestHandleGroundTruthFiles(unittest.TestCase):
    @patch('components.debug.compare.msquickcmp.common.utils.get_batch_index', return_value=1)
    @patch('components.debug.compare.msquickcmp.common.utils.shutil.copy')
    @patch('components.debug.compare.msquickcmp.common.utils.os.walk')
    def test_handle(self, mock_walk, mock_copy, mock_batch):
        from components.debug.compare.msquickcmp.common.utils import handle_ground_truth_files
        parser_mock = MagicMock()
        parser_mock.get_dynamic_scenario_info.return_value = (None, 'DYM_BATCH')
        mock_walk.return_value = [('/root', [], ['x.bin'])]
        with patch('components.debug.compare.msquickcmp.common.utils.BATCH_SCENARIO_OP_NAME', '{}_{}'):
            handle_ground_truth_files(parser_mock, '/npu', '/golden')


class TestStr2Bool(unittest.TestCase):
    def test_valid_true(self):
        from components.debug.compare.msquickcmp.common.utils import str2bool
        for val in ['yes', 'y', 'true', '1', 'True']:
            self.assertTrue(str2bool(val))

    def test_valid_false(self):
        from components.debug.compare.msquickcmp.common.utils import str2bool
        for val in ['no', 'n', 'false', '0', 'False']:
            self.assertFalse(str2bool(val))

    def test_invalid(self):
        from components.debug.compare.msquickcmp.common.utils import str2bool
        import argparse
        with self.assertRaises(argparse.ArgumentTypeError):
            str2bool("maybe")


class TestMergeCSV(unittest.TestCase):
    @patch('components.debug.compare.msquickcmp.common.utils.load_file_to_read_common_check')
    @patch('components.debug.compare.msquickcmp.common.utils.pd.read_csv')
    @patch('components.debug.compare.msquickcmp.common.utils.pd.concat')
    def test_merge(self, mock_concat, mock_read, mock_load):
        from components.debug.compare.msquickcmp.common.utils import merge_csv
        df_mock = MagicMock()
        mock_read.return_value = df_mock
        mock_concat.return_value = df_mock
        df_mock.drop_duplicates.return_value = df_mock
        df_mock.fillna.return_value = df_mock
        result = merge_csv(['a.csv', 'b.csv'], '/output', 'out.csv')
        self.assertTrue(result.endswith('out.csv'))


class TestSafeDeletePathIfExists(unittest.TestCase):
    @patch("components.debug.compare.msquickcmp.common.utils.get_valid_write_path")
    @patch("components.debug.compare.msquickcmp.common.utils.logger")
    @patch("os.path.exists")
    @patch("os.path.isdir")
    @patch("os.path.isfile")
    @patch("os.remove")
    @patch("shutil.rmtree")
    def test_delete_file(self, mock_rmtree, mock_remove, mock_isfile, mock_isdir, mock_exists, mock_logger, mock_get_valid_path):
        mock_exists.return_value = True
        mock_isdir.return_value = False
        mock_isfile.return_value = True
        mock_get_valid_path.return_value = "test_file.txt"
        safe_delete_path_if_exists("test_file.txt", is_log=True)
        mock_get_valid_path.assert_called_once()
        mock_remove.assert_called_once_with("test_file.txt")
        mock_logger.info.assert_called_once()

    @patch("components.debug.compare.msquickcmp.common.utils.get_valid_write_path")
    @patch("components.debug.compare.msquickcmp.common.utils.logger")
    @patch("os.path.exists")
    @patch("os.path.isdir")
    @patch("os.path.isfile")
    @patch("os.remove")
    @patch("shutil.rmtree")
    def test_delete_folder(self, mock_rmtree, mock_remove, mock_isfile, mock_isdir, mock_exists, mock_logger, mock_get_valid_path):
        mock_exists.return_value = True
        mock_isdir.return_value = True
        mock_isfile.return_value = False
        mock_get_valid_path.return_value = "test_folder"
        safe_delete_path_if_exists("test_folder", is_log=True)
        mock_get_valid_path.assert_called_once()
        mock_rmtree.assert_called_once_with("test_folder")
        mock_logger.info.assert_called_once()


class TestParseJsonFile(unittest.TestCase):
    @patch("components.debug.compare.msquickcmp.common.utils.load_file_to_read_common_check")
    @patch("components.debug.compare.msquickcmp.common.utils.ms_open", read_data='{"key": "value"}')
    @patch("json.load")
    def test_parse_json_success(self, mock_json_load, mock_open_file, mock_load_check):
        mock_load_check.return_value = "file.json"
        mock_json_load.return_value = {"key": "value"}
        result = parse_json_file("file.json")
        mock_load_check.assert_called_once_with("file.json")
        mock_json_load.assert_called_once()
        self.assertEqual(result, {"key": "value"})

    @patch("components.debug.compare.msquickcmp.common.utils.load_file_to_read_common_check")
    @patch("components.debug.compare.msquickcmp.common.utils.ms_open", side_effect=FileNotFoundError("No such file"))
    def test_parse_json_file_not_found(self, mock_open_file, mock_load_check):
        mock_load_check.return_value = "missing.json"
        with self.assertRaises(FileNotFoundError) as context:
            parse_json_file("missing.json")
        self.assertIn("missing.json", str(context.exception))

    @patch("components.debug.compare.msquickcmp.common.utils.load_file_to_read_common_check")
    @patch("components.debug.compare.msquickcmp.common.utils.ms_open")
    @patch("json.load", side_effect=json.JSONDecodeError("Expecting value", "doc", 0))
    def test_parse_json_decode_error(self, mock_json_load, mock_open_file, mock_load_check):
        mock_load_check.return_value = "bad.json"
        with self.assertRaises(RuntimeError) as context:
            parse_json_file("bad.json")
        self.assertIn("bad.json", str(context.exception))


class TestLoadNpyFromBuffer(unittest.TestCase):
    @patch("numpy.frombuffer")
    def test_load_npy_success(self, mock_frombuffer):
        fake_data = b'1234'
        mock_array = MagicMock()
        mock_array.reshape.return_value = "reshaped_array"
        mock_frombuffer.return_value = mock_array
        result = load_npy_from_buffer(fake_data, dtype='int8', shape=(2, 2))
        mock_frombuffer.assert_called_once_with(fake_data, dtype='int8')
        mock_array.reshape.assert_called_once_with((2, 2))
        self.assertEqual(result, "reshaped_array")

    @patch("numpy.frombuffer", side_effect=ValueError("Invalid buffer"))
    def test_load_npy_exception(self, mock_frombuffer):
        result = load_npy_from_buffer(b'invalid', dtype='float32', shape=(3, 3))
        self.assertIsNone(result)
